﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using TaoDingPvServer.Common;
using TaoDingPvServer.Common.Json;

namespace TaoDingPvServer.Network
{
    public class WebsocketClient
    {
        public event Action<JsonValue> OnJsonReceived;
        public event Action OnDisconnect;
        public event Action OnConnect;

        //同步对象
        private object m_SynObject = new object();
        //信号对象
        private ManualResetEvent m_ManualResetEvent = new ManualResetEvent(false);
        Socket m_Socket;
        //握手数据
        int m_RecvHandBytes = 0;
        //数据接收缓冲区
        List<byte> m_Data = new List<byte>();

        private bool m_SocketConnected = false;
        private bool m_WebsocketConnected = false;

        private string m_Ip;
        private int m_Port;

        public WebsocketClient(string Ip, int Port)
        {
            m_Ip = Ip;
            m_Port = Port;
        }
        public bool Connect()
        {
            lock (m_SynObject)
            {
                if (!m_WebsocketConnected)
                {
                    m_ManualResetEvent.Reset();
                    m_SocketConnected = false;
                    m_WebsocketConnected = false;
                    m_RecvHandBytes = 0;
                    m_Data.Clear();

                    m_Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

                    SocketAsyncEventArgs args = new SocketAsyncEventArgs();
                    args.RemoteEndPoint = new IPEndPoint(IPAddress.Parse(m_Ip), m_Port);
                    args.Completed += new EventHandler<SocketAsyncEventArgs>(Connect_Completed);
                    m_Socket.ConnectAsync(args);

                    m_ManualResetEvent.WaitOne(3000);

                    if (!m_WebsocketConnected)
                    {
                        m_RecvHandBytes = 0;
                        m_Data.Clear();

                        m_Socket.Shutdown(SocketShutdown.Both);
                        m_Socket.Dispose();

                    }
                    else
                    {
                        OnConnect?.Invoke();
                    }
                }

                return m_WebsocketConnected;
            }
        }
        public void Disconnect()
        {
            lock (m_SynObject)
            {
                m_WebsocketConnected = false;
                m_RecvHandBytes = 0;
                m_Data.Clear();
                if (m_SocketConnected)
                {
                    m_SocketConnected = false;
                    m_Socket.Shutdown(SocketShutdown.Both);
                    m_Socket.Dispose();
                }
            }
        }
        public bool SendMessage(JsonValue Cmd)
        {
            lock (m_SynObject)
            {
                if (!m_WebsocketConnected)
                {
                    return false;
                }
                byte[] data = BuildFrame(Cmd.Stringify());
                Send(data);
                return true;
            }
        }

        public bool Connected
        {
            get { return m_WebsocketConnected; }
        }

        #region 私有函数

        private int Send(byte[] Data)
        {
            SocketAsyncEventArgs args = new SocketAsyncEventArgs();
            args.SetBuffer(Data, 0, Data.Length);
            m_Socket.SendAsync(args);
            return Data.Length;
        }
        private byte[] BuildFrame(string Data)
        {
            if (Data == null || Data == "")
            {
                return null;
            }
            byte[] data = Encoding.UTF8.GetBytes(Data);
            List<byte> Frame = new List<byte>();
            //FIN=1,RSV1=0，RSV2=0，RSV3=0,OPCODE=1
            Frame.Add(0x81);
            //MASK=1,Payload len
            if (data.Length < 126)
            {
                Frame.Add((byte)(0x80 | data.Length));
            }
            //MASK=1,Extended payload len1
            else if (data.Length < 65536)
            {
                Frame.Add(0x80 | 126);
                Frame.Add((byte)((data.Length >> 8) & 0xff));
                Frame.Add((byte)(data.Length & 0xff));
            }
            //MASK=1,Extended payload len2
            else
            {
                Frame.Add(0x80 | 127);
                Frame.Add(0x00);
                Frame.Add(0x00);
                Frame.Add(0x00);
                Frame.Add(0x00);
                Frame.Add((byte)((data.Length >> 24) & 0xff));
                Frame.Add((byte)((data.Length >> 16) & 0xff));
                Frame.Add((byte)((data.Length >> 8) & 0xff));
                Frame.Add((byte)(data.Length & 0xff));
            }
            //Masking-key
            Random rnd = new Random();
            int MaskKey = rnd.Next();
            byte[] arrMaskKey = new byte[]{
                (byte)(MaskKey & 0xff),
                (byte)((MaskKey >> 8) & 0xff),
                (byte)((MaskKey >> 16) & 0xff),
                (byte)((MaskKey >> 24) & 0xff)
            };
            Frame.AddRange(arrMaskKey);
            //Extension data-无
            //Application data
            for (int i = 0; i < data.Length; i++)
            {
                Frame.Add((byte)(data[i] ^ arrMaskKey[i & 0x03]));
            }
            return Frame.ToArray();
        }
        private void ParseFrame(List<byte> Frame)
        {
            while (true)
            {
                if (Frame.Count < 2)
                {
                    break;
                }
                int OpCode = Frame[0] & 0x0F;
                if (OpCode > 0x0A)
                {
                    Disconnect();
                }
                //是否ping
                if (OpCode == 0x09)
                {
                    Send(new byte[] { 0x8A, 0x00 });
                    Frame.RemoveRange(0, 2);
                    break;
                }
                //获取数据
                int DataLen = Frame[1] & 0x7F;
                int DataOffset = 0;
                if (DataLen < 126)
                {
                    DataOffset = 2;
                }
                else if (DataLen == 126)
                {
                    if (Frame.Count < 4)
                    {
                        break;
                    }
                    DataLen = (Frame[2] << 8) + Frame[3];
                    DataOffset = 4;
                }
                else if (DataLen == 127)
                {
                    if (Frame.Count < 10)
                    {
                        break;
                    }
                    DataLen = Frame[9] + (Frame[8] << 8) + (Frame[7] << 16) + (Frame[6] << 24);//四个字节足够大了，忽略其他四个字节
                    DataOffset = 10;
                }
                if (Frame.Count >= DataOffset + DataLen)
                {
                    if (OnJsonReceived != null)
                    {
                        byte[] Data = new byte[DataLen];
                        for (int i = 0; i < DataLen; i++)
                        {
                            Data[i] = Frame[DataOffset + i];
                        }

                        try
                        {
                            JsonValue value = JsonValue.Parse(Encoding.UTF8.GetString(Data, 0, Data.Length).Trim(new char[] { '\0' }));
                            OnJsonReceived(value);
                        }
                        catch (Exception ex)
                        {
                            Log.Error("recv data parse json error:" + ex.Message);
                        }
                    }
                    Frame.RemoveRange(0, DataOffset + DataLen);
                }
                else
                {
                    break;
                }
            }
        }

        private void Connect_Completed(object sender, SocketAsyncEventArgs e)
        {
            m_SocketConnected = true;
            if (e.SocketError == SocketError.Success)
            {
                //开始接收数据
                var buffer = new byte[2048];
                e.SetBuffer(buffer, 0, buffer.Length);
                e.Completed -= Connect_Completed;
                e.Completed += Receive_Completed;
                m_Socket.ReceiveAsync(e);

                //发送握手包
                string HandStr = "GET / HTTP/1.1\r\n" +
                    "Upgrade: websocket\r\n" +
                    "Connection: Upgrade\r\n" +
                    "Host:" + m_Ip + ":" + m_Port + "\r\n" +
                    "Pragma: no-cache\r\n" +
                    "Cache-Control: no-cache\r\n" +
                    "Sec-WebSocket-Key: RkPS39shCf0qqsDoTZxMBg==\r\n" +
                    "Sec-WebSocket-Version: 13\r\n" +
                    "Sec-WebSocket-Extensions: x-webkit-deflate-frame\r\n\r\n";
                Send(Encoding.UTF8.GetBytes(HandStr));
            }
            else
            {
                m_ManualResetEvent.Set();
            }
        }
        private void Receive_Completed(object sender, SocketAsyncEventArgs e)
        {
            //接收数据
            if (e.SocketError == SocketError.Success && e.BytesTransferred > 0)
            {
                //接收握手应答-129个字节
                if (!m_WebsocketConnected && m_RecvHandBytes < 129)
                {
                    //握手应答数据已接收完成
                    if (e.BytesTransferred + m_RecvHandBytes >= 129)
                    {
                        //非握手应答数据放接收缓冲区
                        int m = 129 - m_RecvHandBytes;
                        int n = e.BytesTransferred + m_RecvHandBytes - 129;
                        for (int i = 0; i < n; i++)
                        {
                            m_Data.Add(e.Buffer[i + e.Offset + m]);
                        }
                        m_RecvHandBytes = 129;
                        m_WebsocketConnected = true;
                        m_ManualResetEvent.Set();
                    }
                    else
                    {
                        m_RecvHandBytes += e.BytesTransferred;
                    }
                }
                //接收通讯数据
                else
                {
                    lock (m_SynObject)
                    {
                        for (int i = 0; i < e.BytesTransferred; i++)
                        {
                            m_Data.Add(e.Buffer[i + e.Offset]);
                        }
                        //从缓冲区中去解析消息
                        ParseFrame(m_Data);
                    }
                }
                m_Socket.ReceiveAsync(e);
            }
            else
            {
                Disconnect();
                OnDisconnect?.Invoke();
            }
        }
        #endregion
    }
}